package com.fengwk.support.util;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.beanutils.BeanUtilsBean;
import org.apache.commons.beanutils.PropertyUtilsBean;

import com.fengwk.support.exception.BaseException;
import com.fengwk.support.util.LoggerUtils.Logger;

import java.util.Set;

/**
 * javabean工具
 */
public final class BeanUtils {

	private static final Logger LOG = LoggerUtils.getLogger(BeanUtils.class);

	private static final String BLANK_BEAN_JSON = "{}";
	
	private BeanUtils() {
		throw new BaseException("can not instance " + getClass());
	}

	/**
	 * 通过list bean对象中的某个字段映射为map bean
	 * 
	 * @param filedName
	 * @param list
	 * @param valueClass
	 * @return
	 */
	public static <K, V> Map<K, List<V>> listToMap(String filedName, List<V> list) {
		Map<K, List<V>> map = new LinkedHashMap<K, List<V>>();
		if (CollectionUtils.isBlank(list)) {
			return map;
		}
		V temp = list.get(0);
		try {
			PropertyDescriptor pd = new PropertyDescriptor(filedName, temp.getClass());
			Method getMethod = pd.getReadMethod();
			for (V v : list) {
				@SuppressWarnings("unchecked")
				K key = (K) getMethod.invoke(v);
				List<V> vList = map.get(key);
				if (CollectionUtils.isBlank(vList)) {
					vList = new ArrayList<V>();
					map.put(key, vList);
				}
				vList.add(v);
			}
		} catch (IntrospectionException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
			e.printStackTrace();
		}
		return map;
	}

	/**
	 * bean对象的继承
	 * 
	 * @param src
	 * @param tarClass
	 * @return
	 */
	public static <T> T extend(Object src, Class<T> tarClass) {
		return JsonUtils.fromJson(JsonUtils.toJson(src), tarClass);
	}

	/**
	 * java bean 对象的字段值拷贝
	 * 
	 * @param src 源对象
	 * @param tar 目标对象
	 * @param params 带上此参数表示选择性拷贝
	 * @throws InvocationTargetException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @throws NullPointerException
	 */
	public static void copy(Object src, Object tar, String... params)
			throws NullPointerException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		// 目标检测
		if (src == null) {
			throw new NullPointerException("src is null");
		}
		if (tar == null) {
			throw new NullPointerException("tar is null");
		}

		// 开始拷贝
		Map<String, PropertyDescriptor> srcPdMap = getStringPropertyDescriptorsMap(src.getClass(), params);
		Map<String, PropertyDescriptor> tarPdMap = getStringPropertyDescriptorsMap(tar.getClass(), params);

		if (MapUtils.isBlank(srcPdMap) || MapUtils.isBlank(tarPdMap)) {
		    LOG.debug("unable to get to the appropriate java bean method");
			return;
		}

		Iterator<Entry<String, PropertyDescriptor>> iterator = srcPdMap.entrySet().iterator();
		while (iterator.hasNext()) {
			Entry<String, PropertyDescriptor> entry = iterator.next();
			PropertyDescriptor srcPd = entry.getValue();
			PropertyDescriptor tarPd = tarPdMap.get(entry.getKey());
			if (srcPd == null || tarPd == null) {
				continue;
			}
			// 属性置入
			Method srcRead = srcPd.getReadMethod();
			Method tarWrite = tarPd.getWriteMethod();
			if (srcRead == null || tarWrite == null) {
				continue;
			}
			grantAccessible(tarWrite).invoke(tar, grantAccessible(srcRead).invoke(src));
		}
	}

	/**
	 * java bean 对象字段为空判断(field == null)判断(该字段应当为严格的 java bean 字段,拥有对应的 java
	 * bean read 方法)
	 * 
	 * @param bean 严格的 java bean 对象
	 * @param fields 可选变量,存在时只判断该参数变量是否为空
	 * @return
	 */
	public static boolean isBlank(Object bean, String... fields) {
		return blankBase(true, bean, fields);
	}

	/**
	 * java bean 对象字段非空判断(field != null)判断(该字段应当为严格的 java bean 字段,拥有对应的 java
	 * bean read 方法)
	 * 
	 * @param bean 严格的 java bean 对象
	 * @param fields 可选变量,存在时只判断该参数变量是否为空
	 * @return
	 */
	public static boolean isNotBlank(Object bean, String... fields) {
		return blankBase(false, bean, fields);
	}

	/**
	 * 赋予访问权限
	 * 
	 * @param accessibleObject
	 * @return
	 */
	public static <T extends AccessibleObject> T grantAccessible(T accessibleObject) {
		if (!accessibleObject.isAccessible()) {
			accessibleObject.setAccessible(true);
		}
		return accessibleObject;
	}

	/**
	 * 获取clazz字节码中所有Field和PropertyDescriptor的Map映射
	 * 
	 * @param clazz
	 * @param params 带有此参数表示选择性获取,不带全部获取
	 * @return
	 */
	public static Map<Field, PropertyDescriptor> getFieldPropertyDescriptorsMap(Class<?> clazz, String... params) {
		boolean selected = ArrayUtils.isNotBlank(params);
		// 字段检测
		Field[] fields = getAllField(clazz);
		if (fields == null || fields.length <= 0) {
		    LOG.debug("fields is blank");
		}
		Map<Field, PropertyDescriptor> pdMap = new HashMap<Field, PropertyDescriptor>();
		// 遍历
		for (int i = 0; i < fields.length; i++) {
			if (fields[i] == null || (selected && !StringUtils.contains(fields[i].getName(), params, false))) {
				continue;
			}
			PropertyDescriptor pd = getPropertyDescriptor(fields[i].getName(), clazz);
			if (pd == null) {
				continue;
			}
			pdMap.put(fields[i], pd);
		}
		return pdMap;
	}

	/**
	 * 获取clazz字节码中所有FieldName和PropertyDescriptor的Map映射
	 * 
	 * @param clazz
	 * @param params
	 * @return
	 */
	public static Map<String, PropertyDescriptor> getStringPropertyDescriptorsMap(Class<?> clazz, String... params) {
		boolean selected = ArrayUtils.isNotBlank(params);
		// 字段检测
		Field[] fields = getAllField(clazz);
		if (fields == null || fields.length <= 0) {
		    LOG.debug("fields is blank");
		}
		Map<String, PropertyDescriptor> pdMap = new HashMap<String, PropertyDescriptor>();
		// 遍历
		for (int i = 0; i < fields.length; i++) {
			if (fields[i] == null || (selected && !StringUtils.contains(fields[i].getName(), params, false))) {
				continue;
			}
			PropertyDescriptor pd = getPropertyDescriptor(fields[i].getName(), clazz);
			if (pd == null) {
				continue;
			}
			pdMap.put(fields[i].getName(), pd);
		}
		return pdMap;
	}

	private static Field[] getAllField(Class<?> clazz) {
		Set<Field> fieldSet = new HashSet<Field>();
		recursiveSuperClazzField(clazz, fieldSet);
		return CollectionUtils.toArray(fieldSet, Field.class);
	}

	private static void recursiveSuperClazzField(Class<?> clazz, Set<Field> fieldSet) {
		Set<Field> filedSet = ArrayUtils.toSet(clazz.getDeclaredFields());
		if (CollectionUtils.isNotBlank(filedSet)) {
			fieldSet.addAll(filedSet);
		}
		Class<?> superClass = clazz.getSuperclass();
		if (superClass != null) {
			recursiveSuperClazzField(superClass, fieldSet);
		}
	}

	// 获取Java Bean的PropertyDescriptor
	private static PropertyDescriptor getPropertyDescriptor(String propertyName, Class<?> beanClass) {
		try {
			return new PropertyDescriptor(propertyName, beanClass);
		} catch (IntrospectionException e) {
			// logger.error(e.getMessage());
		}
		return null;
	}

	// 为isBlank方法与isNotBlank方法服务
	// blank参数为true时为isBlank方法, false时为isNotBlank方法
	private static boolean blankBase(boolean blank, Object bean, String... fields) {
		Map<String, PropertyDescriptor> pdMap = getStringPropertyDescriptorsMap(bean.getClass(), fields);
		if (MapUtils.isBlank(pdMap)) {
			return false;
		}
		Iterator<Entry<String, PropertyDescriptor>> iterator = pdMap.entrySet().iterator();
		boolean selected = ArrayUtils.isNotBlank(fields);
		while (iterator.hasNext()) {
			Entry<String, PropertyDescriptor> entry = iterator.next();
			if (selected && !StringUtils.contains(entry.getKey(), fields, false)) {
				continue;
			}
			Method method = entry.getValue().getReadMethod();
			if (method == null) {
				continue;
			}
			Object field = null;
			try {
				field = grantAccessible(method).invoke(bean);
			} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
				e.printStackTrace();
			}
			if (blank) {
				if (field != null) {
					return false;
				}
			} else {
				if (field == null) {
					return false;
				}
			}
		}
		return true;
	}

	/**
	 * Bean对象的转换
	 * 
	 * @param src
	 * @param tarClass
	 * @return
	 */
	public static <S, T> T convert(S src, Class<T> tarClass) {
		if (src == null)
			return null;
		if (tarClass == null)
			throw new BaseException("tarClass == null");
		
		if (TypeUtils.nearObjectSuper(src.getClass()) == TypeUtils.nearObjectSuper(tarClass)) 
			return JsonUtils.fromJson(JsonUtils.toJson(src), tarClass);
		else {
			T dest = JsonUtils.fromJson(BLANK_BEAN_JSON, tarClass);
			
			if (src instanceof Map) {
				// use org.apache.commons.beanutils
				BeanUtilsBean utils = BeanUtilsBean.getInstance();
				PropertyUtilsBean propertyUtils = utils.getPropertyUtils();
	            // Map properties are always of type <String, Object>
				@SuppressWarnings("unchecked")
				final Map<String, Object> propMap = (Map<String, Object>) src;
	            for (final Map.Entry<String, Object> entry : propMap.entrySet()) {
	                final String name = entry.getKey();
	                if (propertyUtils.isWriteable(dest, name)) {
	                	try {
							utils.copyProperty(dest, name, entry.getValue());
						} catch (IllegalAccessException | InvocationTargetException e) {
						    LOG.error(e);
							throw new BaseException(e.getMessage());
						}
	                }
	            }
			} else {
				Class<?> destClass = dest.getClass();
				Class<?> origClass = src.getClass();
				
				List<Field> fs = FieldUtils.getAllFieldsList(origClass);
				for (Field f: fs) {
					String name = f.getName();
					
					Field destF = FieldUtils.getDeclaredField(destClass, name, true);
					Field origF = FieldUtils.getDeclaredField(origClass, name, true);
					
					if (destF == null)
						continue;
					
					int destM =  destF.getModifiers();
					int origM =  origF.getModifiers();
					
					if (Modifier.isStatic(origM))
						continue;
					
					if (Modifier.isFinal(destM))
						continue;
					
					if (destF.getType() != origF.getType())
						continue;
					
					try {
						destF.set(dest, origF.get(src));
					} catch (IllegalArgumentException | IllegalAccessException e) {
					    LOG.error(e);
						throw new BaseException(e.getMessage());
					}
				}
				return dest;
			}
		}
		
		throw new BaseException("convert fail");
	}
	
}
